#ifndef STATE_MACHINE_H_GGJ
#define STATE_MACHINE_H_GGJ

#include "State.h"
#include "../Utils/AssertUtils.h"

template <class Entity>
class CStateMachine
{
public:
	CStateMachine(Entity* entity);
	virtual ~CStateMachine();

	void ChangeState(CState<Entity>* state);
	void SetGlobalState(CState<Entity>* state);
	void SetCurrentState(CState<Entity>* state);
	void SetPreviousState(CState<Entity>* state);
	
	void Update(float dt) const;
	void RevertState();

	CState<Entity>* GetGlobalState() const;
	CState<Entity>* GetCurrentState() const;
	CState<Entity>* GetPreviousState() const;
protected:
	Entity* owner;
	CState<Entity>* globalState;
	CState<Entity>* currentState;
	CState<Entity>* previousState;
};

template <class Entity>
inline CStateMachine<Entity>::CStateMachine(Entity* entity) 
: owner(entity)
{
	globalState = NULL;
	currentState = NULL;
	previousState = NULL;
}

template <class Entity>
inline CStateMachine<Entity>::~CStateMachine() { /* Empty destructor. */ }

template <class Entity>
inline void CStateMachine<Entity>::SetGlobalState(CState<Entity>* state)
{
	globalState = state;
}

template <class Entity>
inline void CStateMachine<Entity>::SetCurrentState(CState<Entity>* state)
{
	currentState = state;
}

template <class Entity>
inline void CStateMachine<Entity>::SetPreviousState(CState<Entity>* state)
{
	previousState = state;
}

template <class Entity>
inline void CStateMachine<Entity>::Update(float dt) const
{
	// if we have a globalState, execute it.
	if (globalState)
	{
		globalState->Execute(owner, dt);
	}

	// if we have a currentState, execute it.
	if (currentState)
	{
		currentState->Execute(owner, dt);
	}
}

template <class Entity>
inline void CStateMachine<Entity>::ChangeState(CState<Entity>* newState)
{
	if (!newState)
	{
		DebugAssert(false, "StateMachine: Attempt to change to a null state.");
		return;
	}

	if (newState == currentState)
	{
		DebugAssert(false, "StateMachine: Attempt to change to the already current state.");
		return;
	}

	// set the current State as the previous one,
	// then call exit() to leave it properly.
	previousState = currentState;
	currentState->Exit(owner);

	// set the current State to the new one,
	// then call enter() to set it up properly.
	currentState = newState;
	currentState->Enter(owner);
}

template <class Entity>
inline void CStateMachine<Entity>::RevertState()
{
	changeState(previousState);
}

template <class Entity>
inline CState<Entity>* CStateMachine<Entity>::GetGlobalState() const
{
	return globalState;
}

template <class Entity>
inline CState<Entity>* CStateMachine<Entity>::GetCurrentState() const
{
	return currentState;
}

template <class Entity>
inline CState<Entity>* CStateMachine<Entity>::GetPreviousState() const
{
	return previousState;
}

#endif // STATE_MACHINE_H_GGJ